/*
 * Copyright 2014 OGIS-RI All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jp.co.ogis_ri.optaplanner.vehiclerouting;

import java.time.LocalDateTime;
import java.util.Date;

import org.optaplanner.core.api.domain.solution.Solution;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import org.optaplanner.core.api.solver.event.SolverEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.Break;
import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.Cargo;
import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.Location;
import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.Vehicle;
import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.VehicleRoutingSolution;
import jp.co.ogis_ri.optaplanner.vehiclerouting.domain.core.ChainedTimeWindowedStep;

/**
 *
 *
 */
public class App implements SolverEventListener {

    protected final transient Logger logger = LoggerFactory.getLogger(this
            .getClass());

    private long startDateTime;
  //年月日時分秒を指定
    LocalDateTime d = LocalDateTime.now();

    /**
     *
     * @param args
     */
    public static void main(final String[] args) {

        App app = new App();

        app.exxcute();
    }

    /**
     *
     */
    public void exxcute() {

        // Build the Solver
        SolverFactory solverFactory = SolverFactory
                .createFromXmlResource("jp/co/ogis_ri/optaplanner/vehiclerouting/solver/config.xml");
        Solver solver = solverFactory.buildSolver();

        // 解く問題を取得
        Solution unsolvedJobSolution = new SolutionGenerator().createSolution();

        // リスナー登録
        solver.addEventListener(this);

        // 開始時間
        this.startDateTime = new Date().getTime();

        // 組合せ最適化を解く
        solver.solve(unsolvedJobSolution);
        Solution solvedSolution = solver.getBestSolution();

        // 最終的なスコアを出力
        this.printScore(solvedSolution);

        // 結果を出力
        String result = toStringSolvedSolution((VehicleRoutingSolution) solvedSolution);
        String cargoData = toStringCargoData((VehicleRoutingSolution) solvedSolution);
        String vehicleData = toStringVehicleData((VehicleRoutingSolution) solvedSolution);

        System.out.println(cargoData);
        System.out.println(vehicleData);
        System.out.println(result);
    }

    /**
     * 割当結果を出力
     *
     * @param solvedSolution
     * @return
     */
    public static String toStringSolvedSolution(
            final VehicleRoutingSolution solvedSolution) {
        StringBuilder displayString = new StringBuilder();

        displayString
//				.append("class\tCapacity\tSize\tReady\tArrival\tDue\tDepartureTime\tServiceDuration\tBreakTime\tLatitude\tLongitude\t\n");
                .append("伝票番号\t対象\t積載量\tサイズ\t作業開始時刻\t到着時刻\t作業終了時刻\t出発時刻\t作業所要時間\t休憩時間\t緯度\t経度\t住所\t前の地点からの距離\t\n");

        for (Vehicle vehicle : solvedSolution.getVehicleList()) {

            if (vehicle.isDummy()) {
                continue;
            }

            displayString.append("\t");
            displayString.append("車両" + "\t");
            displayString.append(new Double(vehicle.getCapacity()) /100 + "\t");
            displayString.append("\t");
            displayString.append("\t");
            displayString.append("\t");
            displayString.append("\t");
            displayString.append(new Double(vehicle.getDepartureTime()) /3600 + "\t");
            displayString.append("\t");
            displayString.append("\t");
            displayString.append(vehicle.getDepot().getLocation().getLatitude()
                    + "\t");
            displayString.append(vehicle.getDepot().getLocation()
                    .getLongitude()
                    + "\t");
            displayString.append(vehicle.getDepot().getLocation().getName()
                    + "\t");
            displayString.append("\t");
            displayString.append("\n");

            ChainedTimeWindowedStep nextStep = vehicle.getNextStep();

            int preDepartureTime = 0;
            Location preLocation = null;

            while (nextStep != null) {

                if (nextStep instanceof Cargo) {
                    Cargo cargo = (Cargo) nextStep;

//                    displayString.append(cargo.getClass().getSimpleName()
                    displayString.append(cargo.getLocation().getOrderNo()
                            + "\t");
                    displayString.append("納品先"
                            + "\t");
                    displayString.append("\t");
                    displayString.append(new Double(cargo.getSize()) /100 + "\t");
                    displayString.append(new Double(cargo.getReadyTime()) /3600 + "\t");
                    displayString.append(new Double(cargo.getArrivalTime()) /3600 + "\t");
                    displayString.append(new Double(cargo.getDueTime()) /3600 + "\t");
                    displayString.append(new Double(cargo.getDepartureTime()) /3600 + "\t");
                    displayString.append(new Double(cargo.getServiceDuration()) /3600 + "\t");
                    displayString.append("\t");
                    displayString.append(cargo.getLocation().getLatitude()
                            + "\t");
                    displayString.append(cargo.getLocation().getLongitude()
                            + "\t");
                    displayString.append(cargo.getLocation().getName()
                            + "\t");
                    displayString.append(cargo.getDistanceToPreviousStep()
                            + "\t");
                    displayString.append("\n");

                } else if (nextStep instanceof Break) {
                    Break breakValue = (Break) nextStep;

                    displayString.append("\t");
                    displayString.append("休憩"
                            + "\t");
                    displayString.append("\t");
                    displayString.append("\t");
                    displayString.append(breakValue.getReadyTime() + "\t");
                    displayString.append(breakValue.getArrivalTime() + "\t");
                    displayString.append(breakValue.getDueTime() + "\t");
                    displayString.append(breakValue.getDepartureTime() + "\t");
                    displayString.append("\t");
                    displayString.append(breakValue.getBreakTime() + "\t");
                    displayString.append(breakValue.getLocation().getLatitude()
                            + "\t");
                    displayString.append(breakValue.getLocation()
                            .getLongitude() + "\t");
                    displayString.append(breakValue.getLocation().getName()
                            + "\t");
                    displayString.append("\t");
                    displayString.append("\n");
                }

                //
                preDepartureTime = nextStep.getDepartureTime();
                preLocation = nextStep.getLocation();
                nextStep = nextStep.getNextStep();
            }

            if (preLocation != null && !vehicle.isDummy()) {
                int arrivalTime = vehicle.getLocation().getTravelTime(
                        preLocation)
                        + preDepartureTime;
                displayString.append("\t");
                displayString.append("車両" + "\t");
                displayString.append(new Double(vehicle.getCapacity()) /100 + "\t");
                displayString.append("\t");
                displayString.append("\t");
                displayString.append(new Double(arrivalTime) /3600 + "\t");
                displayString.append(new Double(vehicle.getDueTime()) /3600 + "\t");
                displayString.append("\t");
                displayString.append("\t");
                displayString.append("\t");
                displayString.append(vehicle.getDepot().getLocation()
                        .getLatitude()
                        + "\t");
                displayString.append(vehicle.getDepot().getLocation()
                        .getLongitude()
                        + "\t");
                displayString.append(vehicle.getDepot().getLocation().getName()
                        + "\t");
                displayString.append(vehicle.getDepot().getLocation().getDistance(preLocation)
                        + "\t");
                displayString.append("\n");
            }

            displayString.append("\n");
        }
        return displayString.toString();
    }

    /**
     * 荷物の一覧を出力
     *
     * @param solvedSolution
     * @return
     */
    public static String toStringCargoData(
            final VehicleRoutingSolution solvedSolution) {
        StringBuilder displayString = new StringBuilder();

        displayString.append("伝票番号\t場所\t緯度\t経度\tサイズ\t準備完了時間\t集荷期限時間\t積込に必要な時間\n");

        for (ChainedTimeWindowedStep chainedTimeWindowedStep : solvedSolution
                .getCargoList()) {

            if (chainedTimeWindowedStep instanceof Cargo) {
                Cargo cargo = (Cargo) chainedTimeWindowedStep;

                displayString.append(cargo.getLocation().getOrderNo() + "\t");
                displayString.append(cargo.getLocation().getName() + "\t");
                displayString.append(cargo.getLocation().getLatitude() + "\t");
                displayString.append(cargo.getLocation().getLongitude() + "\t");
                displayString.append(cargo.getSize() + "\t");
                displayString.append(cargo.getReadyTime() + "\t");
                displayString.append(cargo.getDueTime() + "\t");
                displayString.append(cargo.getServiceDuration() + "\t");
                displayString.append("\n");
            }
        }

        return displayString.toString();
    }

    /**
     * 車両の一覧を出力
     *
     * @param solvedSolution
     * @return
     */
    public static String toStringVehicleData(
            final VehicleRoutingSolution solvedSolution) {
        StringBuilder displayString = new StringBuilder();

        displayString.append("営業所\t緯度\t経度\t容量\t稼働可能開始時間\t稼働可能終了時間\n");

        for (Vehicle vehicle : solvedSolution.getVehicleList()) {

            if (vehicle.isDummy()) {
                continue;
            }

            displayString.append(vehicle.getLocation().getName() + "\t");
            displayString.append(vehicle.getLocation().getLatitude() + "\t");
            displayString.append(vehicle.getLocation().getLongitude() + "\t");
            displayString.append(vehicle.getCapacity() / 100 + "\t");
            displayString.append(vehicle.getReadyTime() + "\t");
            displayString.append(vehicle.getDueTime() + "\t");
            displayString.append("\n");
        }

        return displayString.toString();
    }

    /**
     *
     */
    @Override
    public void bestSolutionChanged(final BestSolutionChangedEvent event) {
        this.printScore(event.getNewBestSolution());
    }

    /**
     *
     * @param solution
     */
    private void printScore(final Solution solution) {

        this.logger.debug("TaskName:TaskName0 " + "ElapsedTime:"
                + (new Date().getTime() - this.startDateTime) + "ms BestScore:"
                + solution.getScore().toString());
    }
}
